#include <vector>
#include <fstream>

#include "Engine.h"
#include "Ray.h"
#include "Primitive.h"

namespace Pandora
{
	float CEngine::srgbEncode(float c)
	{
		if (c <= 0.0031308f)
		{
			return 12.92f * c; 
		}
		else
		{
			return 1.055f * powf(c, 0.4166667f) - 0.055f;
		}
	}

	void CEngine::RenderFrame()
	{
		scene.GetCamera()->Init();
		CColor output = CColor(0.0f, 0.0f, 0.0f);
	
		int x, y;
		#pragma omp parallel for private(x, y, output)
		for(y = 0; y < scene.GetHeight(); ++y)
		{
			for(x = 0; x < scene.GetWidth(); ++x)
			{
				for(float fragmentx = (float)x; fragmentx < (float)x + 1.0f; fragmentx += 0.5f)
				{
					for(float fragmenty = (float)y; fragmenty < (float)y + 1.0f; fragmenty += 0.5f)
					{
						CRay viewRay;
						viewRay.SetStartPoint(scene.GetCamera()->GetPosition());
						scene.GetCamera()->CalculateRayDirection(viewRay, fragmentx, fragmenty);
						CColor temp = TraceRay(viewRay, 1.0f, 1);

						//float exposure = -1.00f;
						//temp.r = 1.0f - expf(temp.r * exposure);
						//temp.g = 1.0f - expf(temp.g * exposure);
						//temp.b = 1.0f - expf(temp.b * exposure);

						output += 0.25f * temp;
					}
				}
				
				//output.r = srgbEncode(output.r);
				//output.g = srgbEncode(output.g);
				//output.b = srgbEncode(output.b);
				screenBuffer[y][x] = output;
				output = CColor(0.0f, 0.0f, 0.0f);
			}
		}

	}

	CColor CEngine::TraceRay(CRay viewRay, float coef, int level)
	{
		CColor out = CColor(0.0f, 0.0f, 0.0f);
		float t = INF;
		int result;
		CPrimitive* currentPrimitive = NULL;

		for (std::vector<CPrimitive*>::iterator i = scene.primitiveContainer.begin(); i != scene.primitiveContainer.end(); ++i) 
		{
			int res = (*i)->Intersect(viewRay, t);
			if(res != 0)
			{
				currentPrimitive = (*i);
				result = res;
			}
		}
		if(currentPrimitive == NULL)
			return out;

		CVector3 newStart = viewRay.GetStartPoint() + viewRay.GetDirection()*t;
		currentPrimitive->GetNormal(newStart).Normalize();

		CRay shadowRay;
		shadowRay.SetStartPoint(newStart);
		for(std::vector<CLight*>::iterator i = scene.lightContainer.begin(); i != scene.lightContainer.end(); ++i)
		{
			shadowRay.SetDirection((*i)->GetPosition() - newStart);
				
			if(currentPrimitive->GetNormal(newStart).Dot(shadowRay.GetDirection()) <= 0.0f)
				continue;

			shadowRay.GetDirection().Normalize();

			bool inShadow = false;
			t = shadowRay.GetDirection().Length();

			if(t == 0.0f)
				continue;

			for (std::vector<CPrimitive*>::iterator j = scene.primitiveContainer.begin(); j != scene.primitiveContainer.end(); ++j)
			{
				if((*j)->Intersect(shadowRay, t) != MISS)
				{
					inShadow = true;
					break;
				}
			}

			if(!inShadow)
			{
				//Lambert
				float lambert = shadowRay.GetDirection().Dot(currentPrimitive->GetNormal(newStart)) * coef;
				out += lambert * (*i)->GetColor() * currentPrimitive->GetMaterial().GetColor();

				//Blinn-Phong
				CVector3 blinnDir = shadowRay.GetDirection() - viewRay.GetDirection();
				if(blinnDir.Length() != 0.0f)
				{
					blinnDir.Normalize();
					float blinn = std::max(blinnDir.Dot(currentPrimitive->GetNormal(newStart)), 0.0f);
					blinn = powf(blinn, currentPrimitive->GetMaterial().GetSpecularPower()) * coef;
					out += blinn * currentPrimitive->GetMaterial().GetSpecular();
				}
			}
		}

		coef *= currentPrimitive->GetMaterial().GetReflection();
		if(result != INSIDE)
		{
			level++;
			if(coef > 0.0f && level < REFLECTIONS)
			{
				//Reflection
				float reflet = 2.0f * (viewRay.GetDirection().Dot(currentPrimitive->GetNormal(newStart)));
				viewRay.SetDirection(viewRay.GetDirection() - reflet * currentPrimitive->GetNormal(newStart));
				viewRay.SetStartPoint(newStart + viewRay.GetDirection()*0.00001f);
				out += TraceRay(viewRay, coef, level);
			}
		}
			
		if(coef > 0.0f && level < REFLECTIONS)
		{
			//Refraction
			if(currentPrimitive->GetMaterial().GetRefraction() != 0.0f)
			{
				float n;
				CVector3 normal = currentPrimitive->GetNormal(newStart) * float(result);

				if(result != INSIDE)
					n = scene.GetEnviromentRefraction() / currentPrimitive->GetMaterial().GetRefraction();
				else
					n = currentPrimitive->GetMaterial().GetRefraction() / scene.GetEnviromentRefraction();

				float cosI = -normal.Dot(viewRay.GetDirection());
				float cosT2 = 1.0f - n * n * (1.0f - cosI * cosI);

				if (cosT2 > 0.0f)
				{
					CVector3 T = (n * viewRay.GetDirection()) + (n * cosI - sqrtf( cosT2 )) * normal;
					viewRay.SetStartPoint(newStart +T*EPSILON);
					viewRay.SetDirection(T);

					//Beer's Law
					if(result == INSIDE)
					{
						CColor BeersLaw;
						BeersLaw.r = BeersLaw.g = BeersLaw.b =expf(0.15f * -t);
						out *= BeersLaw;
						out += TraceRay(viewRay, coef, level);
					}
					else
						out += TraceRay(viewRay, coef, level - 1);
				}
			}
		}

		return out;
	}

	void CEngine::SaveToFile(char* filename)
	{
		std::ofstream imageFile(filename,std::ios_base::binary);
		if (!imageFile)
			return;

		// Addition of the TGA header
		imageFile.put(0).put(0);
		imageFile.put(2);        /* RGB not compressed */

		imageFile.put(0).put(0);
		imageFile.put(0).put(0);
		imageFile.put(0);

		imageFile.put(0).put(0); /* origin X */ 
		imageFile.put(0).put(0); /* origin Y */

		imageFile.put((unsigned char)(scene.GetWidth() & 0x00FF)).put((unsigned char)((scene.GetWidth() & 0xFF00) / 256));
		imageFile.put((unsigned char)(scene.GetHeight() & 0x00FF)).put((unsigned char)((scene.GetHeight() & 0xFF00) / 256));
		imageFile.put(24);       /* 24 bit bitmap */ 
		imageFile.put(0); 
		// end of the TGA header

		for(int y = scene.GetHeight()-1; y >= 0; --y)
			for(int x = scene.GetWidth()-1; x >= 0; --x)
				imageFile.put((unsigned char)std::min(screenBuffer[y][x].b*255.0f,255.0f)).put((unsigned char)std::min(screenBuffer[y][x].g*255.0f, 255.0f)).put((unsigned char)std::min(screenBuffer[y][x].r*255.0f, 255.0f));

		imageFile.close();
	}
	
	void CEngine::InitializeScreenBuffer()
	{
		screenBuffer = new CColor*[scene.GetHeight()]; 
			for(int i = 0; i < scene.GetHeight(); i++) 
				screenBuffer[i] = new CColor[scene.GetWidth()]; 

			for(int i = 0; i < scene.GetHeight(); i++)
				for(int j = 0; j < scene.GetWidth(); j++)
					screenBuffer[i][j] = CColor(0.0f, 0.0f, 0.0f);
	}

};